XML 파일 매핑

스프링의 XML 설정파일에서 <bean>태그 안에 SQL 정보를 넣어놓고 활용하는 건 좋은 방법이 아니다. 그보다는 SQL을 저장해두는 전용 포맷을 가진 독립적인 파일을 이용하는 편이 바람직하다.

XML파일에서 SQL을 읽어뒀다가 DAO에게 제공해주는 SQL 서비스 구현 클래스를 만들어보자.

JAXB

XML에 담긴 정보를 파일에서 읽어오는 방법은 다양하다. 여기서는 JAXB(Java Architecture for XML Binding)을 이용하겠다. JAXB는 XML 문서정보를 거의 동일한 구조의 오브젝트로 직접 매핑해준다는 것이다. XML 정보를 오브젝트처럼 다룰 수 있는 특징을 가지고 있다.

JAXB는 XML 문서의 구조를 정의한 스키마를 이용해서 매핑할 오브젝트의 클래스까지 자동으로 만들어주는 컴파일러도 제공해준다. 자동생성 된 오브젝트에는 매핑정보가 애노테이션으로 담겨 있는데, 애노테이션에 담긴 정보를 이용해서 XML과 매핑된 오브젝트 트리 사이의 자동변환 작업을 수행해준다.

### SQL 맵을 위한 스키마 작성과 컴파일

### 언마샬링 XML문서를 읽어서 자바의 오브젝트로 변환하는 것을 JAXB에서는 언마샬링(unmarshalling)이라고 부른다. 반대로 바인딩 오브젝트를 XML 문서로 변환하는 것은 마샬링(marshalling)이라고 한다.

XML 파일을 이용하는 SQL 서비스

SQL 맵 XML 파일

스프링의 설정 파일에 <map>으로 만들어뒀던 SQL을 모두 옮기면 된다.

XML SQL 서비스

sqlmap.xml에 있는 SQL을 가져와 DAO에 제공해주는 SqlService인터페이스의 구현 클래스를 만들어보자.

고려할 사항은, 언제 JAXB를 사용해 XML문서를 가져와야 하는가이다. 특별한 이유가 없는 한 XML 파일은 한 번만 읽도록 해야 한다.

일단, 간단히 생성자에서 SQL을 읽어와 내부에 저장해두는 초기 작업을 하자.

생성자에서 JAXB를 이용해 XML로 된 SQL 문서를 읽어들이고, 변환된 Sql 오브젝트들을 맵으로 옮겨서 저장해뒀다가, DAO의 요청에 따라 SQL을 찾아서 전달하는 방식으로 SqlService를 구현해보자.

빈의 초기화 작업

현재 XmlSqlSerivce코드에서의 문제점이다. 1. 생성자에서 복잡한 초기화 작업을 다루는 것은 좋지 않다.
일단 초기 상태를 가진 오브젝트를 만들어놓고 별도의 초기화 메소드를 사용하는 방법이 바람직하다.

  1. 읽어들일 파일의 위치와 이름이 코드에 고정되어 있다. 코드의 로직과 여타 이유로 바뀔 가능성이 있는 내용은 외부에서 DI로 설정해줄 수 있게 만들어야 한다.

빈의 제어권은 스프링에 있다. 생성은 물론 초기화도 스프링이 담당하고 있다. 그래서 스프링은 빈 오브젝트를 생성하고 DI작업을 수행해서 프로퍼티를 모두 주입해준 뒤에 미리 지정한 초기화 메소드를 호출해주는 기능을 갖고 있다. 여기서는 빈 후처리기를 사용하여 초기화 후에 loadSql()메소드가 실행 될 수 있도록 하자.

변화를 위한 준비 : 인터페이스 분리

현재 XmlSqlService는 특정 포맷의 XML에서 SQL 데이터를 가져오고, 이를 HashMap타입의 맵 오브젝트에 저장해둔다. SQL을 가져오는 방법에 있어서는 특정 기술에 고정되어 있다. 다른 포맷의 파일에서 SQL을 읽어오게 하려면 어떻게 해야 할까?

지금까지 해왔듯이 서로 관심이 다른 코드들을 분리하고, 서로 코드에 영향을 주지 않으면서 유연하게 확장 가능하도록 DI를 적용해보자.

책임에 따른 인터페이스 정의

가장 먼저 할 일은 분리 가능한 관심사를 구분해보는 것이다. 독립적으로 변경 가능한 책임을 뽑아보자.

  1. SQL 정보를 외부의 리소스로부터 읽어온다.

  2. 읽어온 SQL을 보관해두고 있다가 필요할 때 제공해주는 것이다.

  3. 부가적으로 한 번 가져온 SQL을 필요에 따라 수정할 수 있게 한다.(서버를 재시작 하거나 애플리케이션을 재설치하지 않고도 SQL을 긴급히 변경해야 하는 경우가 있다.)

자기참조 빈으로 시작하기

다중 인터페이스 구현과 간접 참조

SqlService의 구현 클래스는 이제 SqlReaderSqlRegistry두 개의 프로퍼티를 DI 받을 수 있는 구조로 만들어야 한다. XmlSqlService클래스 하나가 SqlService, SqlReader, SqlRegistry라는 세 개의 인터페이스를 구현하게 만들자.

디폴트 의존관계

확장 가능한 인터페이스를 정의하고 인터페이스에 따라 메소드를 구분해서 DI가 가능하도록 코드를 재구성하는 데 성공했다. 다음은 이를 완전히 분리해두고 DI로 조합해서 사용하게 만드는 단계다.

확장 가능한 기반 클래스

SqlRegistrySqlReader를 이용하는 가장 간단한 SqlService 구현 클래스를 만들어보자.

디폴트 의존관계를 갖는 빈 만들기

BaseSqlServicesqlReadersqlRegistry프로퍼티의 DI를 통해 의존관계를 자유롭게 변경해가면서 기능을 확장할 수 있다.

특정 의존 오브젝트가 대부분의 환경에서 거의 디폴트라고 해도 좋을 만큼 기본적으로 사용될 가능성이 있다면, 디폴트 의존관계를 갖는 빈을 만드는 것을 고려해볼 필요가 있다.

단점 ?

설정을 통해 다른 구현 오브젝트를 사용하게 해도 DefaultSqlService는 생성자에서 일단 디폴트 의존 오브젝트를 다 만들어버린다는 점이다.

Issue

Q1. 왜 UserDao와 같은 패키지에 넣었을 때 xml파일을 불러오지 못하는가? [7.2.2]


 public XmlSqlService() {
        String contextPath = Sqlmap.class.getPackage().getName();

        try{
            JAXBContext context = JAXBContext.newInstance(contextPath);
            Unmarshaller unmarshaller = context.createUnmarshaller();
            // InputStream is = UserDao.class.getResourcesAsStream("sqlmap.xml"); => 찾지 못함. 
            InputStream is = getClass().getResourceAsStream("/sql/sqlmap.xml");
            Sqlmap sqlmap = (Sqlmap)unmarshaller.unmarshal(is);

            for(SqlType sql : sqlmap.getSql()){
                sqlMap.put(sql.getKey(), sql.getValue());
            }
        } catch (JAXBException e) {
            throw new RuntimeException(e);
        }
    }